package main

import (
	"fmt"
	"math/rand"
	"os"
	"time"
)

func main() {
	// TODO: Add your code here
	err := SaveData1("data.txt", []byte("Hello, World!"))
	if err != nil {
		panic(err)
		return
	}

}

// SaveData1 saves data to a file
// 如果文件不存在，则创建文件
// 如果文件存在，则覆盖文件
// 存在以下问题：
// 1. 每次都是更新整个文件，而不是修改部分内容，这也是为什么excel不能作为数据库的原因
// 2. 每次都是打开旧的文件，然后覆盖，如果写中途出现问题，比如程序崩溃了怎么办
// 3. 如果需要并发读写，怎样保证并法的安全性
func SaveData1(path string, data []byte) error {
	fp, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
	if err != nil {
		return err
	}
	defer fp.Close()
	_, err = fp.Write(data)
	if err != nil {
		return err
	}
	return fp.Sync() // fsync， 确保数据写入磁盘， 不调用不能保证数据一定写入到磁盘里面了
	//return nil
}

func randomInt() int {
	// 创建一个带种子的随机数生成器
	source := rand.NewSource(time.Now().UnixNano())
	randomGenerator := rand.New(source)

	// 生成一个随机整数
	return randomGenerator.Int()
}

// SaveData2 保存数据到文件，使用临时文件，并使用重命名的方式来保证原子操作
// 1. 如果文件中途出现错误，不会覆盖原先的文件
// 2. 读取文件不会读取到写入一半的数据，保证了数据的完整性
// rename 只是将linux底层的inode节点指向新的文件
// 调用rename之后old file是否存在和但是的状态有关，如果old file当时还有其他打开的句柄就不会删除
// 但是但多个写同时出现时会有问题，这也是为什么需要 append-only logs的原因
func SaveData2(path string, data []byte) error {
	tmp := fmt.Sprintf("%s.tmp.%d", path, randomInt())
	fp, err := os.OpenFile(tmp, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0644)
	if err != nil {
		return err
	}
	defer func() {
		fp.Close()
		// 如果成功，会使用重命名
		if err != nil {
			os.Remove(tmp)
		}
	}()

	_, err = fp.Write(data)
	if err != nil {
		return err
	}
	err = fp.Sync()
	if err != nil {
		return err
	}
	return os.Rename(tmp, path)
}
